﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Reflector.CodeModel;

namespace CallGraphExtractor
{
    [DebuggerDisplay("{MemberReference.Name} in {Module} containerModule")]
    public struct ExtendedMemberReference
    {
        public ExtendedMemberReference(IModule module, IMemberReference memberReference,
            ITypeDeclaration outerTypeDeclaration)
            : this()
        {
            this.Module = module;
            this.MemberReference = memberReference;
            this.OuterTypeDeclaration = outerTypeDeclaration;
#if DEBUG
            ITypeDeclaration td;
            if (memberReference.DeclaringType.TryGetTypeDeclaration(out td) == true)
                if (ExtendedType.GetIsNested(td, outerTypeDeclaration) == true && outerTypeDeclaration == null)
                    ;
#endif //DEBUG
        }

        public IModule Module { get; private set; }
        public IMemberReference MemberReference { get; private set; }
        public ITypeDeclaration OuterTypeDeclaration { get; private set; }
    }

    [DebuggerDisplay("{Type} nested in {OuterType} in {Module} containerModule")]
    public struct ExtendedType
    {
        public ExtendedType(IModule module, IType type, IType outerType = null)
            : this()
        {
            if (module == null)
                throw new ArgumentNullException("containerModule");
            if (type == null)
                throw new ArgumentNullException("type");
            this.Module = module;
            this.Type = type;
            this.OuterType = outerType;
            ITypeDeclaration td;
            type.TryGetTypeDeclaration(out td);
            if (GetIsNested(td, outerType))
                this.IsNested = true;
        }

        public IModule Module { get; private set; }
        public IType Type { get; private set; }
        public IType OuterType { get; private set; }

        public bool IsNested { get; private set; }

        public bool IsValid { get { return this.IsNested == false || (this.IsNested == true && this.OuterType != null); } }

        #region Members

        /// <summary>
        /// Gets a boolean value, that describes wether the type declaration is nested, or not.
        /// </summary>
        /// <remarks>if <paramref name="td">td</paramref> is null, the return value is false</remarks>
        /// <param name="td"></param>
        /// <param name="outerType"></param>
        /// <returns></returns>
        public static bool GetIsNested(ITypeDeclaration td, IType outerType)
        {
            return td != null
                && (outerType != null
                    || td.Visibility == TypeVisibility.NestedPrivate
                    || td.Visibility == TypeVisibility.NestedPublic
                    || td.Visibility == TypeVisibility.NestedFamily
                    || td.Visibility == TypeVisibility.NestedAssembly
                    || td.Visibility == TypeVisibility.NestedFamilyAndAssembly
                    || td.Visibility == TypeVisibility.NestedFamilyOrAssembly)
                && td.Visibility != TypeVisibility.Public && td.Visibility != TypeVisibility.Private;
        }

        public override bool Equals(object obj)
        {
            ExtendedType et;
            try
            {
                et = (ExtendedType)obj;
            }
            catch (InvalidCastException)
            {
                return false;
            }
            return this.Module == et.Module && this.Type == et.Type
                || this.Module.Equals(et.Module) && this.Type.Equals(et.Type);
        }

        public override int GetHashCode()
        {
            int modulHC = this.Module == null ? 1 : this.Module.GetHashCode();
            int typeHC = this.Type == null ? 1 : this.Type.GetHashCode();
            return modulHC ^ typeHC;
        }

        #endregion Members
    }

    public struct CallGraphEdge
    {
        public CallGraphEdge(ExtendedMemberReference emr, CallTypes callType)
            : this()
        {
            this.ExtendedMemberReference = emr;
            this.CallType = callType;
        }

        public ExtendedMemberReference ExtendedMemberReference { get; private set; }
        public CallTypes CallType { get; private set; }
    }

    public class CallGraphEdgeCollection
    {
        public ExtendedMemberReference Caller { get; private set; }
        public ReadOnlyCollection<CallGraphEdge> Called { get; private set; }

        public CallGraphEdgeCollection(ExtendedMemberReference caller,
            IList<CallGraphEdge> called)
        {
            this.Caller = caller;
            this.Called = new ReadOnlyCollection<CallGraphEdge>(called);
        }

        //public void SetCalled(IList<IMemberReference> called)
        //{
        //    this.Called = new ReadOnlyCollection<IMemberReference>(called);
        //}
    }
}
